#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include "ClientConnection.h"

#ifdef USEMNL
#include "MNLClient.h"
#endif

ClientConnection::ClientConnection()
{
}

ClientConnection::ClientConnection(char *host, unsigned int len, unsigned short port)
{
	struct hostent *hent;
	in_addr_t addr;
	int optVal = 1;
	_tz.tz_minuteswest = 0;

	// Get the host entity.  If gethostbyname fails, try gethostbyaddr.
	// Hacky way to circumvent parsing the input.
	if((hent = gethostbyname(host)) == NULL) {
		addr = inet_addr(host); // inet_aton(host,&addr);
		if((hent = gethostbyaddr((const char*)&addr, sizeof addr, AF_INET)) == NULL) {
			printf("ERROR -- %s is neither a host name nor an IP address\n", host);
			exit(-1);
		}
	}
	
	// Create socket for sending
	if((_fdSend = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("ERROR -- Network Error: Cannot create socket for sending");
		exit(-1);
	}

	// Create socket for receiving
	if((_fdRecv = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("ERROR -- Network Error: Cannot create socket for receiving");
		exit(-1);
	}

	// Set socket to non-blocking
	if(fcntl(_fdRecv, F_SETFL, O_NONBLOCK) < 0) {
		perror("ERROR -- Could not set receive socket to non-blocking");
		exit(-1);
	}

	// Reuse the port.                                                                         
	if (setsockopt(_fdRecv, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof(int)) < -1) {
		perror("ERROR -- setsockopt() failed");
		exit(-1);
	}
	
	// Clear the structs
	memset(&_dstAddr, 0, sizeof(struct sockaddr_in));
	memset(&_srcAddr, 0, sizeof(struct sockaddr_in));
	
	_dstPort = port;
	
	// Populate the dstAddr fields
	_dstAddr.sin_family = AF_INET;
	_dstAddr.sin_port = htons(port);
	_dstAddr.sin_addr = *((struct in_addr *) hent->h_addr);
	
	// Populate the srcAddr fields
	_srcAddr.sin_family = AF_INET;
	_srcAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	// Scan ports and try to bind to an available one.
	if(bindToAvailablePort() < 0) {
		perror("ERROR -- Network Error: Cannot bind receiving socket");
		exit(-1);
	}

#ifdef USEMNL
	if(MNLClientInit() != 1) {
		printf("ERROR - MNL Client failed to initialize\n");
		exit(-1);
	}
#endif
	
	// Start the clock
	clock();
	
}

/**
 * connect() - Attempt to connect to the host specified in the constructor
 * 
 * Return Values
 * 		1 for success
 * 		-1 for failure
 */
int ClientConnection::connect() {

	struct timeval timeSeed;
	
	gettimeofday(&timeSeed,&_tz);
	
	_maxAcked = ((rand() >> 20)+(timeSeed.tv_usec % (1 << 12)))/16;
	_maxSent = _maxAcked;
	_timeoutCount = 0;
	_slowStartThresh = 0xFFFFFFFF;
	_numDups = 0;
	_CWND = MSS;
	_offeredWindow = 0;
	_deviation = 0;
	_RTO = 3000000;
			
	if(sendAndWaitForACK(SYN) < 0) {
		printf("ERROR -- Error waiting for ACK of SYN\n");
		return -1;
	} else {
		return 1;
	}		
}

/**
 * bindToAvailablePort() - Scan for an open port to bind, starting at StartPort.
 * 
 * Return values -
 * 		1 for success
 * 		-1 for failure
 */
int ClientConnection::bindToAvailablePort() {

	// Bind receiving socket to local address and port number
	for(unsigned short portToTry = StartPort; portToTry < StartPort+NumPortsToTry; portToTry++) {
		_srcAddr.sin_port = htons(portToTry);
		if(bind(_fdRecv, (sockaddr *) &_srcAddr, sizeof(_srcAddr)) >= 0) {
			_srcPort = portToTry;
			if(verbose)
				printf("Bound to port %d\n", _srcPort);
			return 1;
		}
	}

	return -1;
}

/**
 * send() - Send a packet (unreliable).  Need to check for ACKs
 * 
 * Return values -
 * 		1 for success
 * 		-1 for failure
 */
int ClientConnection::sendPacket(Packet p) {
	int ipError = 0;
	char *buffer = p.toBytes();
	int sendAmt = p.getSize();

	int justSent = 0;
	int retVal = 1;

	while(sendAmt > 0) {
#ifdef USEMNL
		justSent = 	MNL_sendto(_fdSend, buffer, sendAmt, 0, (struct sockaddr *) &_dstAddr, sizeof(_dstAddr),
						p.getSeqNo(), p.getPayloadLength(), p.getAdvWindow(), &ipError);
#else
		justSent = sendto(_fdSend, buffer, sendAmt, 0, (struct sockaddr*)&_dstAddr, sizeof(_dstAddr));
#endif
		
		if(justSent < 0) {
			retVal = -1;
			break;
		}
		if(ipError == 1) {
			printf("ERROR -- ipError is 1\n");
			retVal = -1;
			break;
		}
		sendAmt -= justSent;
	}
	p.freeBytes();
	return retVal;
}

/**
 * packetizeAndSend(char *buf, int beginSeqNo, int numBytes)
 * 
 * Packetize and send data in buf, with sequence number starting at
 * beginSeqNo.  This send is unreliable -- must wait for ACKs.
 * 
 * Returns 1 upon success, -1 upon error.
 */
int ClientConnection::packetizeAndSend(char *buf, unsigned int beginSeqNo, unsigned int numBytes) {
	unsigned int finalSeqNo = beginSeqNo + numBytes - 1;
	int size = 0;
	if(verbose) {
		printf("packetizeAndSend called with beginSeqNo: %d, numBytes: %d\n",beginSeqNo,numBytes);
	}
	while(beginSeqNo <= finalSeqNo) {
		size = (finalSeqNo-beginSeqNo+1 > MSS) ? MSS : finalSeqNo-beginSeqNo+1;
		Packet p = Packet(_srcPort, _dstPort, beginSeqNo, 0, DAT, buf, size);
		if(sendPacket(p) < 0) {
			printf("ERROR -- sendPacket() failed for data packet\n");
			return -1;
		} else {
			beginSeqNo += size;
			buf += size;
		}
	}

	if(finalSeqNo > _maxSent) {
		_maxSent = finalSeqNo;
		if(verbose) {
			printf("maxSent updated to %d\n",_maxSent);
		}
	} else {
		if(verbose) {
			printf("maxSent did not change -- finalSeqNo: %d, _maxSent: %d\n",finalSeqNo,_maxSent);
		}
	}
	
	return 1;
}

/**
 * send(char *buf, int numBytes)
 * 
 * Reliably send all data in buf.  The caller must call this function multiple
 * times to ensure reliable transfer of a large file.
 * 
 * Returns 1 upon success, -1 upon failure.
 * 
 */
int ClientConnection::send(char *buf, unsigned int numBytes) {

	unsigned int sendWindow = 0;

	unsigned int recvBufSize = sizeof(GNUT_Header_t)+MSS;
	char recvBuf[recvBufSize];

	struct timeval startTime;
	struct timeval endTime;

	struct sockaddr_in from;
	unsigned int fromLength = sizeof(struct sockaddr_in);

	unsigned int firstSeqNo = _maxAcked+1;
	unsigned int lastAckNo = firstSeqNo + numBytes;
	
	_timeoutCount = 0;

	printf("-------------------------------------------------\n");
	printf("-- send() called with numBytes = %d\n", numBytes);
	printf("-- maxAcked: %d, maxSent: %d, lastAckNo: %d, CWND: %d, OfferedWindow:%d\n",_maxAcked,_maxSent, lastAckNo, _CWND, _offeredWindow);
	printf("-------------------------------------------------\n");
	// Send the first data packet.
	sendWindow = minInt(_CWND, _offeredWindow,lastAckNo-_maxSent-1);
	packetizeAndSend(buf,firstSeqNo,sendWindow);

	gettimeofday(&startTime,&_tz);;
	
	while(_maxAcked+1 < lastAckNo && _timeoutCount < MaxTimeouts) {
		int bytesReceived = recvfrom(_fdRecv, recvBuf, recvBufSize, 0, (sockaddr *) &from, (socklen_t*)&fromLength);
		if(bytesReceived < 0) {
			if(errno == EWOULDBLOCK) {
				gettimeofday(&endTime,&_tz);
				if((timeDiff(&startTime,&endTime) - _RTO) > 0) {
					_timeoutCount++;
					_RTO = min(2*_RTO, MaxWait);
					_slowStartThresh = max(MSS, _CWND/2);
					_CWND = MSS;
					_maxSent = _maxAcked;
					_numDups = 0;
					// Retransmission Timeout
					sendWindow = minInt(_CWND, _offeredWindow,lastAckNo-_maxSent-1);
					if(verbose) {
						printf("Timed out (RTO now %f)\n maxAcked: %d, maxSent: %d, sendWindow: %d, SSTHRESH: %d\n", _RTO, _maxAcked, _maxSent, sendWindow, _slowStartThresh);
					}
					packetizeAndSend(buf+_maxAcked-firstSeqNo+1, _maxAcked+1, sendWindow);
					gettimeofday(&startTime,&_tz);				
				}
			} else {
				perror("ERROR -- Failed recvFrom");
				exit(-1);
			}
		} else if(bytesReceived > 0) {
			Packet p = Packet(recvBuf,bytesReceived);
			if(p.isACK() && p.checkCheckSum()) {
				_timeoutCount = 0;
				if(	p.getSrcPort() == _dstPort &&
					p.getDstPort() == _srcPort &&
					p.getAckNo() == _maxAcked+1 &&
					p.getAdvWindow() == _offeredWindow) {
					_numDups++;
					if(_numDups == 3) {
						// Fast Recovery
						_slowStartThresh = max(MSS, _CWND/2);
						_CWND = _slowStartThresh;
						_numDups = 0;
						sendWindow = minInt(_CWND, _offeredWindow, lastAckNo-_maxSent-1);
						if(verbose) {
							printf("NACK (Ack# %d) received -- maxAcked: %d, maxSent: %d, sendWindow: %d, CWND: %d\n", p.getAckNo(), _maxAcked, _maxSent, sendWindow, _CWND);
						}
						packetizeAndSend(buf+_maxAcked-firstSeqNo+1, _maxAcked+1, sendWindow);
						gettimeofday(&startTime,&_tz);
					} else {
						continue;
					}
				} else {
					_offeredWindow = p.getAdvWindow();
					if(p.getAckNo() < _maxAcked+2) {
						continue;
					} else {
						// New packet!
						_maxAcked = p.getAckNo()-1;
						gettimeofday(&endTime,&_tz);
						if(_numDups == 0) {
							// New sampled RTT.  Recompute RTO.
							double SRTT = 1.0*timeDiff(&startTime,&endTime);
							_estRTT = Alpha*_estRTT + (1-Alpha)*SRTT;
							double diff = SRTT - _estRTT;
							_deviation = _deviation + Delta*(abs(diff)-_deviation);
							printf("Measured RTT -- double:%f, int: %d",SRTT, timeDiff(&startTime,&endTime));
							printf("RTO changes from %f to ",_RTO);
							_RTO = min(Mu*_estRTT + Phi*_deviation, MaxWait);
							printf("%f\n",_RTO);
						}
						_numDups = 0;
						if(_CWND >= _slowStartThresh) {
							_CWND += (unsigned int) 1.0*MSS*MSS/_CWND;
						} else {
							_CWND += MSS;
						}
						_maxSent = max(_maxSent,_maxAcked);
						sendWindow = minInt(_CWND,_offeredWindow,lastAckNo-_maxSent-1);
						if(_maxAcked + sendWindow > _maxSent) {
							// TODO: Double check what buf should point to
							if(verbose) {
								printf("Sliding window -- _maxAcked: %d, _maxSent: %d, sendWindow: %d, CWND: %d\n", _maxAcked, _maxSent, sendWindow, _CWND);
							}
							packetizeAndSend(buf+_maxSent-firstSeqNo+1,_maxSent+1,_maxAcked+sendWindow-_maxSent);
						}
						gettimeofday(&startTime,&_tz);
					}
				}
			} else if(p.isRST() && p.checkCheckSum()) {
				printf("Received RST during data transfer -- terminating connection\n");
				return -1;
			} else {	// Not an ACK or RST... what is it?
				printf("Unexpected packet:\n");
				p.printPacket();
			}
		} else {
			if(verbose) {
				printf("DEBUG -- recvfrom() returned 0 while waiting for data packet\n");
			}
		}
	}
	
	if(_timeoutCount == MaxTimeouts) {
		printf("Connection timed out %d times -- terminating connection\n",MaxTimeouts);
		Packet rstPacket = Packet(_srcPort, _dstPort, _maxAcked+1, 0, RST, 0, 0);
		sendPacket(rstPacket);
		return -1;
	} else if(_maxAcked+1 == lastAckNo) {
		if(verbose) {
			printf("Finished sending %d bytes of data packets.\n", numBytes);
		}
		return 1;
	} else {
		printf("ERROR -- _maxAcked exceeds lastAckNo:\n");
		printf("_maxAcked: %d\nlastAckNo:%d\n",_maxAcked,lastAckNo);
		return -1;		
	}
	
}

/**
 * closeConnection() - File transfer completed successfully.  Send FIN and wait for final ACK.
 * 
 * Returns 1 upon success, -1 upon failure.
 */
int ClientConnection::closeConnection() {	
	if(sendAndWaitForACK(FIN) < 0) {
		printf("ERROR -- Error waiting for ACK of FIN\n");
		return -1;
	} else {
		return 1;
	}	
}

/**
 * closeSockets() - Close the open sockets.
 */
void ClientConnection::closeSockets() {
	closesocket(_fdSend);
	closesocket(_fdRecv);
}

int ClientConnection::sendAndWaitForACK(int type) {

	unsigned int recvBufSize = sizeof(GNUT_Header_t)+MSS;
	char recvBuf[recvBufSize];
	
	struct sockaddr_in from;
	unsigned int fromLength = sizeof(struct sockaddr_in);
	
	struct timeval startTime;
	struct timeval endTime;
	
	const char* strType = (type == SYN) ? "SYN" : "FIN";
	
	Packet p = Packet(_srcPort, _dstPort, _maxAcked+1, 0, type, 0, 0);
	
	if(sendPacket(p) < 0) {
		printf("ERROR -- Unable to send %s packet\n", strType);
		return -1;
	}
	if(verbose) {
		printf("Sending %s\n",strType);
		p.printPacket();
	}
	_maxSent = _maxAcked+1;
	gettimeofday(&startTime,&_tz);

	do {
		int bytesReceived = recvfrom(_fdRecv, recvBuf, recvBufSize, 0, (sockaddr *) &from, (socklen_t*)&fromLength);
		if(bytesReceived < 0) {
			if(errno == EWOULDBLOCK) {
				gettimeofday(&endTime,&_tz);
				if((timeDiff(&startTime,&endTime) - _RTO) > 0) {
					_timeoutCount++;
					_RTO = min(2*_RTO, MaxWait);
					if(sendPacket(p) < 0) {
						printf("ERROR -- Unable to resend %s\n", strType);
						return -1;
					}
					if(verbose) {
						printf("Timed out waiting for ACK of %s, resending %s\n",strType,strType);
					}
					gettimeofday(&startTime,&_tz);
				}
			} else {
				perror("ERROR -- Failed recvfrom");
				exit(-1);
			}
		} else if(bytesReceived > 0) {
			Packet receivedPacket = Packet(recvBuf, bytesReceived);
			gettimeofday(&endTime,&_tz);
			if( receivedPacket.getSrcPort() == _dstPort &&
				receivedPacket.getDstPort() == _srcPort &&
				receivedPacket.isACK() &&
				receivedPacket.getAckNo() == _maxAcked+2 &&
				receivedPacket.checkCheckSum()) {
				_offeredWindow = receivedPacket.getAdvWindow();
				_estRTT = (type == SYN) ? timeDiff(&startTime,&endTime) : _estRTT;
				_maxAcked++;
				_timeoutCount = 0;
				if(verbose) {
					if(type == SYN) {
						printf("Connection Established (ACK for SYN received):\n");
						receivedPacket.printPacket();
						return 1;
					} else if(type == FIN) {
						printf("Connection Closed (ACK for FIN received)\n");
						return 1;
					}
				}
				return 1;
			} else if(	receivedPacket.getSrcPort() == _dstPort &&
						receivedPacket.getDstPort() == _srcPort &&
						receivedPacket.isRST() &&
						receivedPacket.checkCheckSum()){
				printf("Received RST in response to %s -- terminating connection\n", strType);
				return -1;
			} else {
				startTime.tv_sec = endTime.tv_sec;
				startTime.tv_usec = endTime.tv_usec;
				if(verbose) {
					printf("Received packet, but not the one we wanted, resetting timeout\n");
					printf("_srcPort: %d\n_dstPort: %d\n_maxAcked: %d\ncheckCheckSum(): %d\n",_srcPort,_dstPort,_maxAcked,receivedPacket.checkCheckSum());
					receivedPacket.printPacket();
				}
			}
		} else {
			if(verbose) {
				printf("DEBUG -- recvfrom() returned 0 while waiting for data packet\n");
			}
		}
	} while (_timeoutCount < MaxTimeouts);

	return -1;
	
}

/**
 * sendFile(int fd)
 * 
 * Send the file associated with file descriptor fd.
 * 
 * Returns 1 upon success, -1 upon failure.
 * 
 */
int ClientConnection::sendFile(int fd) {
	
	int bytesRead = 0;
	char buffer[FileChunkSize];
	while((bytesRead = read(fd, buffer, FileChunkSize)) > 0) {
		if(send(buffer, bytesRead) < 0) {
			printf("ERROR -- Send failed\n");
			return -1;
		}
 	}
	return 1;
}

int ClientConnection::timeDiff(struct timeval *startTime, struct timeval *endTime) {
	return OneMillion*(endTime->tv_sec-startTime->tv_sec)+(endTime->tv_usec-startTime->tv_usec);
}

unsigned int ClientConnection::max(unsigned int x, unsigned int y) {
	return (x > y) ? x : y;
}

unsigned int ClientConnection::minInt(unsigned int x, unsigned int y) {
	return (x > y) ? y : x;	
}

unsigned int ClientConnection::minInt(unsigned int x, unsigned int y, unsigned int z) {
	return minInt(x,minInt(y,z));
}

double ClientConnection::min(double x, double y) {
	return (x - y > 0) ? y : x;
}

double ClientConnection::abs(double x) {
	return (x < 0) ? -x : x;
}

ClientConnection::~ClientConnection()
{
}
